package lv.solcraft.replication.server.boot;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import net.sf.json.JSONObject;

import org.apache.commons.io.FileUtils;
import org.springframework.util.Assert;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.BaseException;

public class ServerConfig {

    private String serverName;

    private int maxOpersPerSession = 10000;

    private int opersBatchSize = 100;

    private int opersTimeToLiveInHours = 24 * 20;

    private String ignoreFields = "";

    private String filterForTables = "*";

    private String tablesWithStoredProcs = "";

    private String filter = "";

    private String sites;

    private String blobFields = "";

    private String retryAnalyserCron = "0 0 22 * * ?";

    private int maxOperRetryCount = 100;

    private String dbPath;

    private List<Long> sitesList;

    private File originFile;

    private File replicationServerHome;

    private String opersCleanerCron = "0 10 22 * * ?";

    private int finishedOpersTimeToLiveInHours = 24 * 5;

    public String getDbPath() {
        return dbPath;
    }

    public int getMaxOpersPerSession() {
        return maxOpersPerSession;
    }

    public void setMaxOpersPerSession(int maxOpersPerSession) {
        this.maxOpersPerSession = maxOpersPerSession;
    }

    public int getOpersBatchSize() {
        return opersBatchSize;
    }

    public void setOpersBatchSize(int opersBatchSize) {
        this.opersBatchSize = opersBatchSize;
    }

    public String getIgnoreFields() {
        return ignoreFields;
    }

    public void setIgnoreFields(String ignoreFields) {
        this.ignoreFields = ignoreFields;
    }

    public String getFilterForTables() {
        return filterForTables;
    }

    public void setFilterForTables(String filterForTables) {
        this.filterForTables = filterForTables;
    }

    public String getTablesWithStoredProcs() {
        return tablesWithStoredProcs;
    }

    public void setTablesWithStoredProcs(String tablesWithStoredProcs) {
        this.tablesWithStoredProcs = tablesWithStoredProcs;
    }

    public String getBlobFields() {
        return blobFields;
    }

    public void setBlobFields(String blobFields) {
        this.blobFields = blobFields;
    }

    public String getFilter() {
        return filter;
    }

    public void setFilter(String filter) {
        this.filter = filter;
    }

    public String getSites() {
        return sites;
    }

    public void setSites(String sites) {
        this.sites = sites;
        this.sitesList = getSitesList(sites);
    }

    public String getRetryAnalyserCron() {
        return retryAnalyserCron;
    }

    public void setRetryAnalyserCron(String retryAnalyserCron) {
        this.retryAnalyserCron = retryAnalyserCron;
    }

    public int getMaxOperRetryCount() {
        return maxOperRetryCount;
    }

    public void setMaxOperRetryCount(int maxOperRetryCount) {
        this.maxOperRetryCount = maxOperRetryCount;
    }

    static List<Long> getSitesList(String sites) {
        Assert.hasText(sites, "Sites string is empty");
        List<Long> sitesList = new ArrayList<Long>();
        for (String site : sites.split(",")) {
            site = site.trim();
            if (!site.equals("")) {
                try {
                    sitesList.add(Long.parseLong(site));
                } catch (NumberFormatException e) {
                    throw new IllegalArgumentException("Invalid sites list: " + sites, e);
                }
            }
        }
        Assert.isTrue(sitesList.size() > 0, "No sites specified");
        Collections.sort(sitesList);
        return Collections.unmodifiableList(sitesList);
    }

    public void copyFrom(ServerConfig serverConfig) {
        this.filter = serverConfig.filter;
        this.filterForTables = serverConfig.filterForTables;
        this.ignoreFields = serverConfig.ignoreFields;
        this.maxOperRetryCount = serverConfig.maxOperRetryCount;
        this.maxOpersPerSession = serverConfig.maxOpersPerSession;
        this.opersBatchSize = serverConfig.opersBatchSize;
        this.retryAnalyserCron = serverConfig.retryAnalyserCron;
        this.serverName = serverConfig.serverName;
        setSites(serverConfig.getSites());
        this.tablesWithStoredProcs = serverConfig.tablesWithStoredProcs;
        this.dbPath = serverConfig.dbPath;
        this.originFile = serverConfig.originFile;
        this.replicationServerHome = serverConfig.replicationServerHome;
        this.blobFields = serverConfig.blobFields;
        this.opersCleanerCron = serverConfig.opersCleanerCron;
        this.retryAnalyserCron = serverConfig.retryAnalyserCron;
        this.finishedOpersTimeToLiveInHours = serverConfig.finishedOpersTimeToLiveInHours;
        this.opersTimeToLiveInHours = serverConfig.opersTimeToLiveInHours;
    }

    public void validate() {
        Assert.hasText(serverName, "Server name is empty");
        Assert.hasText(dbPath, "Database path is empty, server: " + serverName);
    }

    public List<Long> getSitesList() {
        return sitesList;
    }

    @SuppressWarnings("unchecked")
    public static ServerConfig fromXML(final File replicationServerHome, final File serverFile) {
        Assert.notNull(replicationServerHome, "replicationServerHome is null");
        Assert.notNull(serverFile, "serverFile is null");
        String xml;
        try {
            xml = FileUtils.readFileToString(serverFile);
        } catch (IOException e) {
            throw new IllegalArgumentException("IO error while reading servers configuration from " + serverFile.getAbsolutePath(), e);
        }
        XStream xstream = new XStream();
        xstream.setMode(XStream.NO_REFERENCES);
        xstream.alias("server", ServerConfig.class);
        try {
            ServerConfig serverConfig = (ServerConfig) xstream.fromXML(xml);
            serverConfig.setOriginFile(serverFile);
            serverConfig.updateServerName(replicationServerHome, parseServerName(serverFile));
            return serverConfig;
        } catch (BaseException e) {
            throw new IllegalArgumentException("Error while parsing server config xml file: " + xml, e);
        }
    }

    private void setOriginFile(File originFile) {
        this.originFile = originFile;
    }

    public JSONObject toClientJSONReloaded() {
        ServerConfig server = ServerConfig.fromXML(replicationServerHome, originFile);
        return server.toClientJSON();
    }

    private JSONObject toClientJSON() {
        JSONObject json = new JSONObject();
        json.put("maxOpersPerSession", maxOpersPerSession);
        json.put("opersBatchSize", opersBatchSize);
        json.put("ignoreFields", ignoreFields);
        json.put("filterForTables", filterForTables);
        json.put("tablesWithStoredProcs", tablesWithStoredProcs);
        json.put("filter", filter);
        json.put("sites", sitesList);
        json.put("blobFields", blobFields);
        return json;
    }

    public static String parseServerName(File file) {
        Assert.isTrue(file.getName().endsWith(".server.xml"), "Invalid server file name: " + file.getName());
        return file.getName().split("[.]")[0];
    }

    public String getServerName() {
        return serverName;
    }

    void updateServerName(File replicationServerHome, String serverName) {
        this.replicationServerHome = replicationServerHome;
        this.serverName = serverName;
        this.dbPath = replicationServerHome.getAbsolutePath() + File.separator + serverName;
    }

    public String getOpersCleanerCron() {
        return opersCleanerCron;
    }

    public void setOpersCleanerCron(String opersCleanerCron) {
        this.opersCleanerCron = opersCleanerCron;
    }

    public Date calculateOpersExpireTime() {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.HOUR_OF_DAY, -opersTimeToLiveInHours);
        return calendar.getTime();
    }

    public int getOpersTimeToLiveInHours() {
        return opersTimeToLiveInHours;
    }

    public void setOpersTimeToLiveInHours(int opersTimeToLiveInHours) {
        this.opersTimeToLiveInHours = opersTimeToLiveInHours;
    }

    public Date calculateFinishedOpersExpireTime() {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.HOUR_OF_DAY, -finishedOpersTimeToLiveInHours);
        return calendar.getTime();
    }

    public int getFinishedOpersTimeToLiveInHours() {
        return finishedOpersTimeToLiveInHours;
    }

    public void setFinishedOpersTimeToLiveInHours(int finishedOpersTimeToLiveInHours) {
        this.finishedOpersTimeToLiveInHours = finishedOpersTimeToLiveInHours;
    }
}
